package com.vecspace.statistics.counter;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.apache.commons.lang3.StringUtils;

import com.vecspace.utils.TJSON;

/**对时间段内的数量进行计数。内含多个TimeCounter
 * 分别记录当前秒，当前分钟，当时小时，当前天，当前月，以及
 * 前n秒，前n分钟，前n小时，前n天，前n月的数量。
 * 可以在多线程情况下进行计数
 * @author: wanghua
 */
public class TimePeriodCounter {
	
	public TimePeriodCounter() {
		appStartTime = new Date();
		this.name = "";
	}
	
	public TimePeriodCounter(String name) {
		this();
		this.name = name;
	}
	
	private String name;
	private final Date appStartTime;
	
	/**计数开始时间*/
	private long startCounterTime = 0;
	
	/**最后一次计数时间*/
	private long lastCounterTime = 0;
	
	/**是否正在执行reset操作*/
	private boolean locking = false; 
	/**自系统启动以来的计数总数*/
	private AtomicLong total = new AtomicLong(0L);
	/**自最后一次reset之后的计数总数*/
	private AtomicLong lastTotal = new AtomicLong(0L);
	
	
	private final Map<String, TimeCounter> secondsCountMap = new LinkedHashMap<>();
	private final Map<String, TimeCounter> minutesCountMap = new LinkedHashMap<>();
	private final Map<String, TimeCounter> hoursCountMap = new LinkedHashMap<>();
	private final Map<String, TimeCounter> daysCountMap = new LinkedHashMap<>();
	private final Map<String, TimeCounter> monthsCountMap = new LinkedHashMap<>();
		
	private long currSecondTicks = 0;
	private TimeCounter currSecondCounter;
	private TimeCounter currMinuteCounter;
	private TimeCounter currHourCounter;
	private TimeCounter currDayCounter;
	private TimeCounter currMonthCounter;
	

	private TimeCounter preSecondCounter;
	private TimeCounter preMinuteCounter;
	private TimeCounter preHourCounter;
	private TimeCounter preDayCounter;
	private TimeCounter preMonthCounter;
	
	int historySeconds = 180;//记录历史秒速
	int historyMinutes = 180;//记录历史分钟数
	int historyHours = 48;
	int historyDays = 62;
	int historyMonths = 13;
	
	/**当前时间点上增加一个计数
	 * 可以在多线程情况下进行计数
	 * 对应的秒数、分钟数、小时数上分别各增加一个计数
	 * @author: wanghua
	 */
	public void increment() {		
		add(1);
	}
	
	/**当指定的时间点上增加一个计数
	 * 可以在多线程情况下进行计数
	 * 对应的秒数、分钟数、小时数上分别各增加一个计数
	 * @author: wanghua
	 */
	public void increment(long occurTime) {		
		add(1, occurTime);
	}
	
	/**当前时间点上增加delta个计数
	 * 对应的秒数、分钟数、小时数上分别各增加delta个计数
	 * 可以在多线程情况下进行计数
	 * @author: wanghua
	 */
	public void add(int delta) {
		long occurTime = System.currentTimeMillis();
		add(delta , occurTime);
	}
	/**当前时间点上增加delta个计数
	 * 对应的秒数、分钟数、小时数上分别各增加delta个计数
	 * 可以在多线程情况下进行计数
	 * @param delta 计数变化量
	 * @param occurTime 计数的时间
	 * @author: wanghua
	 */
	public void add(int delta, long occurTime) {		
		if(0 == delta)return;
		try {
		total.addAndGet(delta);
		lastTotal.addAndGet(delta);
		//long currTime = occurTime;//
		if(occurTime < 1) occurTime = System.currentTimeMillis();
		if(startCounterTime == 0) {
			startCounterTime = occurTime;//计数开始时间
		}
		lastCounterTime = occurTime;
		while(locking) { //在取得currTime之后再进行判断，因为计数的时间是调用此函数时的时间，而不是等待之后的时间
			//by wanghua 注意：并不能解决多线程reset问题。在执行到此函数后续代码体时，如果进行了reset操作，任然会抛异常
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		long currSecondTicks2 = (long)(occurTime / 1000.0);
		if(currSecondTicks2 != currSecondTicks) {//秒数发生了变化，重新对currXXX对象赋值
			synchronized (TimePeriodCounter.class) {
				if(currSecondTicks2 != currSecondTicks) {
					Calendar calendar = Calendar.getInstance();
					calendar.setTimeInMillis(occurTime);
					int year = calendar.get(Calendar.YEAR);
					int month = calendar.get(Calendar.MONTH);
					int day = calendar.get(Calendar.DAY_OF_MONTH);
					int hour = calendar.get(Calendar.HOUR_OF_DAY);
					int minute = calendar.get(Calendar.MINUTE);
					int second = calendar.get(Calendar.SECOND);
					
					String currSecondStr  = StringUtils.join(String.valueOf(year) , "-" , String.valueOf(month + 1) , "-" , String.valueOf(day) , " " , String.valueOf(hour) , ":" , String.valueOf(minute) , ":" , String.valueOf(second));
					String currMinuteStr  = StringUtils.join(String.valueOf(year) , "-" , String.valueOf(month + 1) , "-" , String.valueOf(day) , " " , String.valueOf(hour) , ":" , String.valueOf(minute));
					String currHourStr  = StringUtils.join(String.valueOf(year) , "-" , String.valueOf(month + 1) , "-" , String.valueOf(day) , " " , String.valueOf(hour));
					String currDayStr = StringUtils.join(String.valueOf(year) , "-" , String.valueOf(month + 1) , "-" , String.valueOf(day));
					String currMonthStr = StringUtils.join(String.valueOf(year) , "-" , String.valueOf(month + 1));
				
					TimeCounter currSecondCounter1 = currSecondCounter;
					currSecondCounter = secondsCountMap.get(currSecondStr);
					if(currSecondCounter == null) {
						preSecondCounter = currSecondCounter1;
						currSecondCounter = new TimeCounter(year, month, day, hour, minute, second);
						secondsCountMap.put(currSecondStr, currSecondCounter);
					}

					TimeCounter currMinuteCounter1 = currMinuteCounter;
					currMinuteCounter = minutesCountMap.get(currMinuteStr);
					if(currMinuteCounter == null) {
						preMinuteCounter = currMinuteCounter1;
						currMinuteCounter = new TimeCounter(year, month, day, hour, minute, 0);
						minutesCountMap.put(currMinuteStr, currMinuteCounter);
						
						clearOverayHisotry();//每分钟清理一次过期的历史数据
					}
					
					TimeCounter currHourCounter1 = currHourCounter;
					currHourCounter = hoursCountMap.get(currHourStr);
					if(currHourCounter == null) {
						preHourCounter = currHourCounter1;
						currHourCounter = new TimeCounter(year, month, day, hour, 0, 0);
						hoursCountMap.put(currHourStr, currHourCounter);
					}

					TimeCounter currDayCounter1 = currDayCounter;
					currDayCounter = daysCountMap.get(currDayStr);
					if(currDayCounter == null) {
						preDayCounter = currDayCounter1;
						currDayCounter = new TimeCounter(year, month, day, 0, 0, 0);
						daysCountMap.put(currDayStr, currDayCounter);
					}

					TimeCounter currMonthCounter1 = currMonthCounter;
					currMonthCounter = monthsCountMap.get(currMonthStr);
					if(currMonthCounter == null) {
						preMonthCounter = currMonthCounter1;
						currMonthCounter = new TimeCounter(year, month, 0, 0, 0, 0);
						monthsCountMap.put(currMonthStr, currMonthCounter);
					}
					currSecondTicks = currSecondTicks2;
				}
			}
		}
		
		currSecondCounter.add(delta);
		currMinuteCounter.add(delta);
		currHourCounter.add(delta);
		currDayCounter.add(delta);
		currMonthCounter.add(delta);
		}catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}
	
	/**清理过期的历史数据
	 * @author: wanghua
	 */
	private void clearOverayHisotry() {
		while(secondsCountMap.size() > historySeconds) {
			String key = secondsCountMap.keySet().iterator().next();
			secondsCountMap.remove(key);
		}
		
		while(minutesCountMap.size() > historyMinutes) {
			String key = minutesCountMap.keySet().iterator().next();
			minutesCountMap.remove(key);
		}
		
		while(hoursCountMap.size() > historyHours) {
			String key = hoursCountMap.keySet().iterator().next();
			hoursCountMap.remove(key);
		}
		
		while(daysCountMap.size() > historyDays) {
			String key = daysCountMap.keySet().iterator().next();
			daysCountMap.remove(key);
		}
		
		while(monthsCountMap.size() > historyMonths) {
			String key = monthsCountMap.keySet().iterator().next();
			monthsCountMap.remove(key);
		}
	}
	
	/**重置计数器
	 * 尽量不要在多线程的情况下使用reset(不禁止使用，只是有可能引发一次内部异常，一次计数不准)
	 * reset是一个线程不安全的操作。在reset的同时，如果有其他线程在计数，会引发错误
	 * @author: wanghua
	 */
	public void reset() {
		locking = true;
		try {
			/**最后一次计数时间*/
			startCounterTime = 0;
			lastCounterTime = 0;			
			lastTotal.set(0);
			secondsCountMap.clear();
			minutesCountMap.clear();
			hoursCountMap.clear();
			daysCountMap.clear();
			monthsCountMap.clear();
				
			currSecondTicks = 0;
			currSecondCounter = null;
			currMinuteCounter = null;
			currHourCounter = null;
			currDayCounter = null;
			currMonthCounter = null;
			

			preSecondCounter = null;
			preMinuteCounter = null;
			preHourCounter = null;
			preDayCounter = null;
			preMonthCounter = null;
		}finally {
			locking = false;
		}
	}
	
	/**设置历史记录保留的格式
	 * 
	 * @param secondSize 按秒计数器保留的个数
	 * @param minuteSize 按分钟计数器保留的个数
	 * @param hourSize
	 * @param daySize
	 * @param monthSize 
	 * @author: wanghua
	 */
	public void setHistorySize(int secondSize, int minuteSize, int hourSize, int daySize, int monthSize) {
		this.historySeconds = secondSize;
		this.historyMinutes = minuteSize;
		this.historyHours = hourSize;
		this.historyDays = daySize;
		this.historyDays = daySize;
	}

	public String getName() {
		return name;
	}

	/**计数器启动的时间（重置时不会改变此时间）
	 * @return 
	 * @author: wanghua
	 */
	public Date getAppStartTime() {
		return appStartTime;
	}
	
	/**返回最后一秒的数据（有数据记录的最后一秒，不一定是当前秒(如果当前分秒没数据产生的话)）；不表示这一秒完整的数据，因为这一秒还没过完
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getLastSecondCounter() {
		if(currSecondCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(currSecondCounter.getTime(), currSecondCounter.getCount());
	}

	/**返回最后一分钟的数据（有数据记录的最后一分钟，不一定是当前分钟(如果当前分钟没数据产生的话)）；不表示这一分钟完整的数据，因为这一分钟还没过完
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getLastMinuteCounter() {
		if(currMinuteCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(currMinuteCounter.getTime(), currMinuteCounter.getCount());
	}

	/**返回最后一小时的数据（有数据记录的最后一小时，不一定是当前小时(如果当前小时没数据产生的话)）；不表示这一小时完整的数据，因为这一小时还没过完
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getLastHourCounter() {
		if(currHourCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(currHourCounter.getTime(), currHourCounter.getCount());
	}
	
	/**返回最后一天的数据（有数据记录的最后一天，不一定是当天(如果当天没数据产生的话)）;不表示这一天完整的数据，因为这一天还没过完
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getLastDayCounter() {
		if(currDayCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(currDayCounter.getTime(), currDayCounter.getCount());
	}
	
	public TimeCount getLastMonthCounter() {
		if(currMonthCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(currMonthCounter.getTime(), currMonthCounter.getCount());
	}

	/**返回倒数第二秒的数据（有数据记录的倒数第二秒，不一定是当前时间的前一秒，也不一定是最后一秒的前一秒）
	 * 比如 5秒，8秒，10秒，则返回的是8秒，而不是9秒
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getPreSecondCounter() {
		if(preSecondCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(preSecondCounter.getTime(), preSecondCounter.getCount());
	}

	/**返回倒数第二分钟的数据（有数据记录的倒数第二分钟，不一定是当前分钟的前一分钟，也不一定是最后一分钟的前一分钟）
	 * 比如9点 3分，5分， 8分，10分，则返回的是8分，而不是9分
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getPreMinuteCounter() {
		if(preMinuteCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(preMinuteCounter.getTime(), preMinuteCounter.getCount());
	}
	
	/**返回当前实际事情的前1分钟的Counter
	 * 
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getRealPreMinuteCounter() {
		Calendar currTime = Calendar.getInstance();
		currTime.add(Calendar.MINUTE, -1);
		Calendar preMinute = currTime;
		long preMinutes = preMinute.getTimeInMillis() / (1000 * 60);
		long lastCounterMinutes = this.getLastMinuteCounter().getTime().getTime() / (1000 * 60);
		if(preMinutes == lastCounterMinutes) {
			return this.getLastMinuteCounter();
		}else {
			long preCounterMinutes = this.getPreMinuteCounter().getTime().getTime() / (1000 * 60);
			if(preMinutes == preCounterMinutes) {
				return this.getPreMinuteCounter();
			}else {
				return null;
			}
		}		
	}
	
	/**返回倒数第二小时的数据（有数据记录的倒数第二小时，不一定是当前小时的前一小时，也不一定是最后一小时的前一小时）
	 * 比如 3点，5点，8点，10点，则返回的是8点，而不是9点
	 * @return 
	 * @author: wanghua
	 */
	public TimeCount getPreHourCounter() {
		if(preHourCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(preHourCounter.getTime(), preHourCounter.getCount());
	}

	public TimeCount getPreDayCounter() {
		if(preDayCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(preDayCounter.getTime(), preDayCounter.getCount());
	}

	public TimeCount getPreMonthCounter() {
		if(preMonthCounter == null)return new TimeCount(new Date(0), 0);
		return new TimeCount(preMonthCounter.getTime(), preMonthCounter.getCount());
	}

	/**获取最后n秒的数据
	 * 返回的数据列表在时间上是顺序排列的
	 * 注意：n秒时间上可能不是连续的，实际返回的是有数据的n秒，如果其中有些时间段内没有数据，则忽略这些秒数。
	 * @return 
	 * @author: wanghua
	 */
	public List<TimeCount> getHistorySecondsCount() {
		clearOverayHisotry();
		return getTimeCounts(secondsCountMap);
	}
	
	/**获取最后n分钟的数据
	 * 返回的数据列表在时间上是顺序排列的
	 * 注意：n分钟时间上可能不是连续的，实际返回的是有数据的n分钟，如果其中有些时间段内没有数据，则忽略这些分钟。
	 * @return 
	 * @author: wanghua
	 */
	public List<TimeCount> getHistoryMinutesCount() {
		clearOverayHisotry();
		return getTimeCounts(minutesCountMap);
	}

	/**获取最后n小时的数据
	 * 返回的数据列表在时间上是顺序排列的
	 * 注意：n小时时间上可能不是连续的，实际返回的是有数据的n小时，如果其中有些时间段内没有数据，则忽略这些小时。
	 * @return 
	 * @author: wanghua
	 */
	public List<TimeCount> getHistoryHoursCount() {
		return getTimeCounts(hoursCountMap);
	}

	public List<TimeCount> getHistoryDaysCount() {
		return getTimeCounts(daysCountMap);
	}

	public List<TimeCount> getHistoryMonthsCount() {
		return getTimeCounts(monthsCountMap);
	}
	
	private List<TimeCount> getTimeCounts(Map<String, TimeCounter> histroyCountMap){
		if(histroyCountMap == null)return Collections.emptyList();
		List<TimeCount> countList = new ArrayList<>();
		for(TimeCounter counter : histroyCountMap.values()) {
			TimeCount timeCount = new TimeCount(counter.getTime(), counter.getCount());
			countList.add(timeCount);
		}
		Collections.sort(countList);
		return countList;
	}
	/**获取自计数以来的总数
	 * 
	 * @return 
	 * @author: wanghua
	 */
	public long getTotal() {
		return total.get();
	}
	
	/**最后一次计数时间（返回值精确毫秒数）
	 * @return 
	 * @author: wanghua
	 */
	public Date getLastTime() {
		return new Date(lastCounterTime);
	}
	
	/**计数开始时间（reset会重置此时间）
	 * 
	 * @return 
	 * @author: wanghua
	 */
	public long getStartCounterTime() {
		return startCounterTime;
	}


	@Override
	public String toString() {
		try {
			return TJSON.toJSONString(this);
		} catch (JsonProcessingException e) {
			e.printStackTrace();
			return null;
		}
	}
}
